//-----------------------------------------------------------------------
// <copyright file="ExceptionProviderBase.cs" company="CodePlex">
//     Copyright (c) Doug Holland, CodePlex. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace CodePlex.Diagnostics.Providers
{
    using System;
    using System.Reflection;
    using System.Configuration.Provider;

    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;

    using System.Security.Principal;

    /// <summary>
    /// Defines the contract that allows for the publication of exceptions through one or more custom exception providers. 
    /// </summary>
    public abstract class ExceptionProviderBase : ProviderBase
    {
        #region protected ExceptionProviderBase()

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ExceptionProviderBase"/> class.    
        /// </summary>
        protected ExceptionProviderBase()
        {
        }

        #endregion

        #region protected ExceptionContext ExceptionContext

        /// <summary>
        /// Gets or sets the <see cref="T:CodePlex.Diagnostics.ExceptionContext"/> containing details regarding the <see cref="T:System.Exception"/> to be published.
        /// </summary>
        protected ExceptionContext ExceptionContext
        {
            get;
            set;
        }

        #endregion

        #region public static ExceptionProviderBase CreateInstance()

        /// <summary>
        /// Creates an instance of the default exception provider.
        /// </summary>
        /// <returns>An instance of the default exception provider.</returns>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "refactorings planned")]
        public static ExceptionProviderBase CreateInstance()
        {
            try
            {
                // retrieve the exception provider configuration.
                ProviderConfiguration configuration = ProviderConfiguration.GetConfiguration();

                // retrieve an instance of the default provider.
                Provider provider = configuration.ExceptionProviders[configuration.DefaultExceptionProvider] as Provider;

                #region Trace.Write()

                Trace.Write("ExceptionProviderBase.CreateInstance() - Default Exception Provider = ");
                Trace.WriteLine(provider.Name);
                Trace.Flush();

                #endregion

                // establish the System.Type for the default provider.
                Type providerType = Type.GetType(provider.Type);

                return Activator.CreateInstance(providerType) as ExceptionProviderBase;
            }
            catch
            {
                // no exception provider configuration could be found or specified provider was not found, return the default provider.
                return new SqlExceptionProvider() as ExceptionProviderBase;
            }
        }

        #endregion
        
        #region public static int? GetHResult(Exception ex)

        /// <summary>
        /// Gets the value of the <see cref="M:System.Exception.HResult"/> property.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> for which the value of the <see cref="M:System.Exception.HResult"/> property is required.</param>
        /// <returns>The value of the <see cref="M:System.Exception.HResult"/> property within the supplied <see cref="T:System.Exception"/> instance.</returns>
        public static int? GetHResult(Exception ex)
        {
            Type type = ex.GetType();

            PropertyInfo[] properties = type.GetProperties(BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
            {
                if (string.Equals(property.Name, "HResult"))
                {
                    return (int)property.GetValue(ex, null);
                }
            }

            #region Trace.WriteLine()

            Trace.WriteLine("CodePlex::Diagnostics::Providers::SqlExceptionProvider::GetHResult()");
            Trace.Indent();
            Trace.WriteLine("Unable to determine HResult value for '");
            Trace.WriteLine(ex.GetType().ToString());
            Trace.WriteLine("'.");
            Trace.Unindent();

            Trace.Flush();

            #endregion

            return null;
        }

        #endregion

        #region public static bool? GetIsTransient(Exception ex)

        /// <summary>
        /// Gets the value of the <see cref="M:System.Exception.IsTransient"/> property.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> for which the value of the <see cref="M:System.Exception.IsTransient"/> property is required.</param>
        /// <returns>The value of the <see cref="M:System.Exception.IsTransient"/> property within the supplied <see cref="T:System.Exception"/> instance.</returns>
        public static bool? GetIsTransient(Exception ex)
        {
            Type type = ex.GetType();

            PropertyInfo[] properties = type.GetProperties(BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
            {
                if (string.Equals(property.Name, "IsTransient"))
                {
                    return (bool)property.GetValue(ex, null);
                }
            }

            #region Trace.WriteLine()

            Trace.WriteLine("CodePlex.Diagnostics.Providers.SqlExceptionProvider.GetIsTransient()");
            Trace.Indent();
            Trace.WriteLine("Unable to determine IsTransient value for '");
            Trace.WriteLine(ex.GetType().ToString());
            Trace.WriteLine("'.");
            Trace.Unindent();

            Trace.Flush();

            #endregion

            return null;
        }

        #endregion

        #region public abstract void Publish(Exception ex, IIdentity identity)

        /// <summary>
        /// Publishes the specified <see cref="T:System.Exception"/> using the configured custom exception provider.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> to be published.</param>
        /// <param name="identity">The <see cref="T:System.Security.Principal.IIdentity"/> to associate with this exception.</param>
        public abstract void Publish(Exception ex, IIdentity identity);

        #endregion

        #region public abstract void Publish(object context)

        /// <summary>
        /// Publishes a <see cref="T:System.Exception"/> instance and associated contextual information.
        /// </summary>
        /// <param name="context">The <see cref="T:CodePlex.Diagnostics.ExceptionContext"/> to be published.</param>
        public abstract void Publish(object context);

        #endregion
    }
}
